package org.iabako.shared.querydefinition.dto;

import com.google.gwt.user.client.rpc.IsSerializable;
import org.iabako.shared.entity.business.CustomFieldValue;
import org.iabako.shared.entity.enumeration.CustomFieldType;
import org.iabako.shared.querydefinition.SearchMotorQueryEnum;
import org.iabako.shared.querydefinition.columndefinition.ColumnDefinition;
import org.iabako.shared.tools.GenericTools;

import java.beans.Transient;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * TODO javadoc
 * Date: 24/11/13
 * Time: 18:22
 */
public class QueryDefinitionDTO implements IsSerializable, Serializable, Cloneable {

    private static final long serialVersionUID = 7440297955003302414L;

    private List<CriteriaFilterDTO> criterias;
    private List<CriteriaFilterDTO> criteriasOR;
    private SearchMotorQueryEnum searchMotorQuery;
    private Map<Long, QueryResultDTO> results;
    private int pageNumber;
    private int currentPage;
    private int pageSize;
    private int countTotal;
    private ColumnDefinition sortedColumn;
    private boolean asc;
    private boolean ascByDefault;
    private boolean customQuery;
    private List<ColumnDefinition> customColumns;
    private List<ColumnDefinition> columnsExclusion;
    private boolean customModifications;
    private List<CustomFieldValue> clientCustomFieldCriterias;
    private String customFieldFilter;
    private Map<String, Serializable> customFieldParameters;

    public ColumnDefinition getSortedColumn() {
        return sortedColumn;
    }

    public void setSortedColumn(ColumnDefinition sortedColumn) {
        this.sortedColumn = sortedColumn;
    }

    public boolean isAsc() {
        return asc;
    }

    public void setAsc(boolean asc) {
        this.asc = asc;
    }

    public QueryDefinitionDTO() {
        ascByDefault = true;
        initialize();
    }

    public int getCountTotal() {
        return countTotal;
    }

    public int getPageSize() {
        return pageSize;
    }

    public int getPageSizeIndex() {
        //TODO change this
        return pageSize == 10 ? 0 : pageSize == 30 ? 1 : pageSize == 50 ? 2 : 3;
    }

    public int getPageNumber() {
        return pageNumber;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }

    public void setCountTotal(int countTotal) {
        this.countTotal = countTotal;
    }

    public void setPageNumber(int pageNumber) {
        this.pageNumber = pageNumber;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public SearchMotorQueryEnum getSearchMotorQuery() {
        return searchMotorQuery;
    }

    public void setSearchMotorQuery(SearchMotorQueryEnum searchMotorQuery) {
        this.searchMotorQuery = searchMotorQuery;
    }

    public boolean isAscByDefault() {
        return ascByDefault;
    }

    public void setAscByDefault(boolean ascByDefault) {
        this.ascByDefault = ascByDefault;
    }

    public List<CriteriaFilterDTO> getCriterias() {
        if (criterias == null) {
            criterias = new ArrayList<CriteriaFilterDTO>();
        }
        return criterias;
    }

    public void addCriteria(ColumnDefinition key, QueryOperatorEnum operator, Serializable value) {
        getCriterias().add(new CriteriaFilterDTO(key, value, operator));
    }

    public List<CriteriaFilterDTO> getCriteriasOR() {
        if (criteriasOR == null) {
            criteriasOR = new ArrayList<CriteriaFilterDTO>();
        }
        return criteriasOR;
    }

    public void addCriteriaOR(ColumnDefinition key, QueryOperatorEnum operator, Serializable value) {
        getCriteriasOR().add(new CriteriaFilterDTO(key, value, operator));
    }

    public Map<Long, QueryResultDTO> getResults() {
        return results;
    }

    public void setResults(Map<Long, QueryResultDTO> results) {
        this.results = results;
    }

    public List<ColumnDefinition> getColumnsExclusion() {
        if (columnsExclusion == null) {
            columnsExclusion = new ArrayList<ColumnDefinition>();
        }
        return columnsExclusion;
    }

    public void addColumnExclusion(ColumnDefinition columnExclusion) {
        getColumnsExclusion().add(columnExclusion);
    }

    public void setCustomQuery(boolean customQuery) {
        this.customQuery = customQuery;
    }

    public boolean isCustomQuery() {
        return customQuery;
    }

    public void addCustomColumn(ColumnDefinition columnDefinition) {
        if (customColumns == null) {
            customColumns = new ArrayList<ColumnDefinition>();
        }
        customColumns.add(columnDefinition);
    }

    public boolean isCustomModifications() {
        return customModifications;
    }

    public void setCustomModifications(boolean customModifications) {
        this.customModifications = customModifications;
    }

    public List<CustomFieldValue> getClientCustomFieldCriterias() {
        if (clientCustomFieldCriterias == null) {
            clientCustomFieldCriterias = new ArrayList<CustomFieldValue>();
        }
        return clientCustomFieldCriterias;
    }

    public void addClientCustomFieldCriterias(CustomFieldValue clientCustomFieldCriteria) {
        this.getClientCustomFieldCriterias().add(clientCustomFieldCriteria);
    }

    public Map<String, Serializable> getCustomFieldParameters() {
        if (customFieldParameters == null) {
            customFieldParameters = new HashMap<String, Serializable>();
        }
        return customFieldParameters;
    }

    public void addCustomFieldParameters(String parameter, Serializable value) {
        getCustomFieldParameters().put(parameter, value);
    }

    public String getCustomFieldFilter() {
        if (customFieldFilter == null) {
            calculateClientCustomFilter();
        }
        return customFieldFilter;
    }

    public void setCustomFieldFilter(String customFieldFilter) {
        this.customFieldFilter = customFieldFilter;
    }

    @Transient
    public String getRequestSQL(List<CriteriaFilterDTO> allCriteria) {
        String requestSQL = "select ";
        requestSQL += searchMotorQuery.getPrincipalAlias() + ".id,";
        requestSQL += searchMotorQuery.getModificationAlias() + ".id,";

        for (ColumnDefinition column : getSelectColumns()) {
            requestSQL += column.getHqlDefinition() + ",";
        }

        //delete last comma
        requestSQL = requestSQL.substring(0, requestSQL.length() - 1);
        requestSQL += " " + searchMotorQuery.getFromPart();

        requestSQL = addJoinsByCriteria(requestSQL, allCriteria);

        return requestSQL;
    }

    private String addJoinsByCriteria(String requestSQL, List<CriteriaFilterDTO> allCriteria) {
        for (CriteriaFilterDTO criteriaFilterDTO : allCriteria) {
            if (!GenericTools.isEmpty(criteriaFilterDTO.getKey().getHqlJoin())
                    && !requestSQL.contains(criteriaFilterDTO.getKey().getHqlJoin())) {
                requestSQL += " " + criteriaFilterDTO.getKey().getHqlJoin();
            }
        }
        return requestSQL;
    }

    @Transient
    public String getRequestCountSQL(List<CriteriaFilterDTO> allCriteria) {
        String countRequestSQL = "select count(distinct " + searchMotorQuery.getPrincipalAlias() + ".id) " + searchMotorQuery.getFromPartWithMandatoryJoins();
        countRequestSQL = addJoinsByCriteria(countRequestSQL, allCriteria);
        return countRequestSQL;
    }

    @Transient
    public List<ColumnDefinition> getOnlyInExcelExportColumns() {
        List<ColumnDefinition> onlyInExcelExportColumns = new ArrayList<ColumnDefinition>();
        for (ColumnDefinition columnDefinition : searchMotorQuery.getColumns()) {
            if (columnDefinition.isOnlyInExcelExport()) {
                onlyInExcelExportColumns.add(columnDefinition);
            }
        }
        return onlyInExcelExportColumns;
    }

    @Transient
    public List<ColumnDefinition> getSelectColumns() {
        if (!customQuery) {
            List<ColumnDefinition> columns = searchMotorQuery.getNoTransientColumns();
            columns.removeAll(getColumnsExclusion());
            return columns;
        } else {
            return customColumns;
        }
    }

    @Transient
    public String getHqlSortedColumn() {
        if (getSortedColumn() == null) {
            return null;
        }
        String hqlSortedColumn = getSortedColumn().getHqlDefinition();
        if (hqlSortedColumn.contains(" as ")) {
            hqlSortedColumn = hqlSortedColumn.substring(hqlSortedColumn.indexOf(" as ") + " as ".length());
        }
        return hqlSortedColumn;
    }

    @Transient
    public void initialize() {
        pageSize = 10;
        currentPage = 1;
        sortedColumn = null;
        asc = ascByDefault;
        criterias = null;
        criteriasOR = null;
        results = null;
        countTotal = 0;
        pageNumber = 0;
        searchMotorQuery = null;
        results = null;
        customQuery = false;
        customFieldFilter = null;
    }

    @Transient
    public void calculateClientCustomFilter() {
        StringBuilder sb = new StringBuilder();

        int index = 0;
        for (CustomFieldValue criteria : getClientCustomFieldCriterias()) {
            Serializable value = null;
            QueryOperatorEnum operator = null;
            String customFieldValueAttribute = null;
            switch (criteria.getCustomField().getCustomFieldType()) {
                case inputText:
                case textarea:
                    value = "%" + criteria.getStringValue() + "%";
                    operator = QueryOperatorEnum.LIKE;
                    customFieldValueAttribute = "stringValue";
                    break;
                case integer:
                    value = criteria.getIntegerValue();
                    operator = QueryOperatorEnum.EQUALS;
                    customFieldValueAttribute = "integerValue";
                    break;
                case decimal:
                    value = criteria.getDoubleValue();
                    operator = QueryOperatorEnum.EQUALS;
                    customFieldValueAttribute = "doubleValue";
                    break;
                case date:
                    value = GenericTools.getDateFromString(criteria.getDateValueString());
                    operator = QueryOperatorEnum.EQUALS;
                    customFieldValueAttribute = "dateValue";
                    break;
                case list:
                    value = criteria.getListItemValue().getId();
                    operator = QueryOperatorEnum.EQUALS;
                    customFieldValueAttribute = "listItemValue.id";
                    break;
            }

            sb.append("(");
            sb.append("SELECT value.id FROM CustomFieldValue value ");
            sb.append("WHERE value.client = client AND ");
            sb.append("value.customField = :customField").append(index).append(" AND ");
            sb.append("value.").append(customFieldValueAttribute).append(operator.getOperator());
            if (criteria.getCustomField().getCustomFieldType() == CustomFieldType.list) {
                sb.append("(");
                sb.append("SELECT itemValue.id FROM CustomFieldListItem itemValue where itemValue.customField = :customField").append(index);
                sb.append(" AND itemValue.id").append(operator.getOperator()).append(":customFieldValue").append(index);
                sb.append(")");
            } else {
                sb.append(" :customFieldValue").append(index);
            }

            sb.append(") IS NOT NULL");

            addCustomFieldParameters("customField" + index, criteria.getCustomField());
            addCustomFieldParameters("customFieldValue" + index, value);

            if (getClientCustomFieldCriterias().indexOf(criteria) != getClientCustomFieldCriterias().size() - 1) {
                sb.append(" AND ");
            }
            index++;
        }
        customFieldFilter = sb.length() > 0 ? sb.toString() : null;
    }

    public void clearCriterias() {
        getCriterias().clear();
        getCriteriasOR().clear();
    }

    public void excludeAllColumns() {
        for (ColumnDefinition columnDefinition : getSelectColumns()) {
            getColumnsExclusion().add(columnDefinition);
        }
    }
}
